
namespace Patterns4Net.Core
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using Mono.Cecil;
    using Patterns4Net.Attributes.DesignPatterns;
    using Patterns4Net.Core.AttributesPatterns;
    using Patterns4Net.Core.CecilExtensions;
    using Patterns4Net.Attributes;

    /// <summary>
    /// Locates patterns using plain .NET attributes, this attributes 
    /// are user for annotation of patterns that are supplied with Pattern4Net.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Location of patterns based on .NET attributes that implement interface 
    /// <see cref="IPattern"/> is done by <see cref="AttributesPatternsProvider"/>.
    /// </para>
    /// </remarks>
    [Immutable]
    [Export(typeof(IPatternsProvider))]
    public sealed class PlainAttributesPatternsProvider : IPatternsProvider
    {
        private readonly IEnumerable<Lazy<ICreatePatternFromAttribute, PatternCreatorExportAttribute>> patternCreators;

        [ImportingConstructor]
        public PlainAttributesPatternsProvider(
            [ImportMany]IEnumerable<Lazy<ICreatePatternFromAttribute, PatternCreatorExportAttribute>> patternCreators)
        {
            this.patternCreators = patternCreators;
        }

        public IEnumerable<IPattern> GetPatterns<TMember>(TMember member) where TMember : IMemberDefinition
        {   
            /* Contracts are inherited. */

            // We get all custom attributes defined on given type
            IEnumerable<CustomAttribute> attributes = GetPatternAttributes(member); 

            var result = new List<IPattern>();

            foreach (var attribute in attributes)
            {
                Contract.Assume(attribute != null);
                Contract.Assume(attribute.AttributeType != null);
                var creator = this.patternCreators.FirstOrDefault(x => attribute.AttributeType.IsEqual(x.Metadata.AttributeType));
                if (creator != null)
                {
                    result.Add(creator.Value.Crate(attribute, member));
                }
            }

            return result;
        }

        /// <summary>
        /// Helper method used to encapsulate query for <see cref="IPatternAttribute"/> attributes.
        /// </summary>
        private static IEnumerable<CustomAttribute> GetPatternAttributes(IMemberDefinition type)
        {
            Contract.Requires(type != null);
            Contract.Ensures(Contract.Result<IEnumerable<CustomAttribute>>() != null);
            Contract.Assume(type.CustomAttributes != null);

            // Select only attributes that implements IPatternAttribute
            return  from attr in type.CustomAttributes
                    let resolved = attr.AttributeType.Resolve() // Resolve attribute so we can get ifaces list.
                    where resolved != null                      // If we cannot resolve it, than leave it...
                    where resolved.Interfaces.Any(x => x.IsEqual(typeof(IPatternAttribute)))  // check that IPatternsAttribute is present
                    select attr;
        }
    }
}
